位操作,只有两个值,0和1,8个位正好是1b,所以位操作是非常节省空间的一种操作。
|
|
1 Byte(B) = 8 bit 1 Kilo Byte(KB) = 1024B 1 Mega Byte(MB) = 1024 KB 1 Giga Byte (GB)= 1024 MB |
在redis中他的用法也非常简单, 基本语法如下:
redis 127.0.0.1:6379> Setbit KEY_NAME OFFSET
原理部分:这个是SETBIT使用方法的简单说明
在redis中,存储的字符串都是以二级制的进行存在的。
举例:
设置一个 key-value ,键的名字叫“andy” 值为字符'a'
我们知道 'a' 的ASCII码是 97。转换为二进制是:01100001。offset的学名叫做“偏移” 。二进制中的每一位就是offset值啦,比如在这里 offset 0 等于 ‘0’ ,offset 1等于'1' ,offset2等于'1',offset 7 等于'1' ,没错,offset是从左往右计数的,也就是从高位往低位。
我们通过SETBIT 命令将 andy中的 'a' 变成 'b' 应该怎么变呢?
也就是将 01100001 变成 01100010 (b的ASCII码是98),这个很简单啦,也就是将'a'中的offset 6从0变成1,将offset 7 从1变成0 。
大家可能也发现了,每次SETBIT完毕之后,有一个(integer) 0或者(integer)1的返回值,这个是在你进行SETBIT 之前,该offset位的比特值。
这个时候,我们再get andy 一下,看看结果:
果然,就从'a' 变成 'b'了。
这就是redis 中 “SETBIT” 的基本用法。
BITCOUNT 就是统计字符串的二级制码中,有多少个'1'。 所以在这里,
BITCOUNT andy 得到的结果就是 3 啦。
一个字符最大255,二进制是11111111, 只有8位,所有最多压缩8倍存储空间
|
|
redis> SETBIT bit 10086 1 #把第10086个位置设置为1 (integer) 0 redis> GETBIT bit 10086 #获取第10086个位置的值 看是0还是1 (integer) 1 redis> GETBIT bit 100 # bit 默认被初始化为 0 (integer) 0 |
其实就是把某个位标记为1或者0而已,但是它的好处在于非常节省空间。另外既然是位,就会涉及到或运算或者与运算(后面会有实例)。
我们来看一个实例吧
场景: 1亿个用户,每个用户登陆/做任意操作,记为 今天活跃,否则记为不活跃。
每周评出: 有奖活跃用户: 连续7天活动
每月评,等等...
其实简单说就是统计一下连续7天(或者连续30天)有多少人连续登陆过
咱们先来想一想传统的方案
很容易就会想到只要用户登陆了,我在表中插入一条数据,并且记录上对应的日期,然后用mysql里面的记录来逐个判断。
但这样是存在一些问题的,主要的问题在于用户量高达1亿,每个用户登陆一次就远远的超过mysql的极限了,更不要说统计一星期了,而且用上group ,sum运算,计算也是非常慢的。所以在这种用户量大,而且统计比较简单的问题上,咱们可以运用位(setbit)操作来解决问题。
先分析一下思路,对于某一天来说,我们可以把这一天想像成一根小木棍,分成了不同的段落,每个段落对应的就是用户的位(因为有user_id),默认值都是0,只要有人登陆了,就把对应的用户的位置标为1即可。

如上图所示,这个就是一天的登陆情况,user_id为6和user_id为8的用户登陆过。其余的都为没有登陆过。因为这个是位操作,所以占的空间很小,1亿的用户,所占的空间也就不到12M。
一天的问题咱们解决了,如何解决他们是否连续登陆过呢?
我们可以用上多个"木棍"

我们可以把每一天作为一个键,然后每天对用户登陆状态进行标记,在最后用每天做一个"与运算"就可以准确的知道哪些用户连续登陆了。
其实总结一下过程如下:
1、记录用户登陆:
每天按日期生成一个位图, 用户登陆后,把user_id位上的bit值置为1
2:、把1周的位图 and 计算,
位上为1的,即是连续登陆的用户
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
redis 127.0.0.1:6379> setbit mon 100000000 0 (integer) 0 redis 127.0.0.1:6379> setbit mon 3 1 (integer) 0 redis 127.0.0.1:6379> setbit mon 5 1 (integer) 0 redis 127.0.0.1:6379> setbit mon 7 1 (integer) 0 redis 127.0.0.1:6379> setbit thur 100000000 0 (integer) 0 redis 127.0.0.1:6379> setbit thur 3 1 (integer) 0 redis 127.0.0.1:6379> setbit thur 5 1 (integer) 0 redis 127.0.0.1:6379> setbit thur 8 1 (integer) 0 redis 127.0.0.1:6379> setbit wen 100000000 0 (integer) 0 redis 127.0.0.1:6379> setbit wen 3 1 (integer) 0 redis 127.0.0.1:6379> setbit wen 4 1 (integer) 0 redis 127.0.0.1:6379> setbit wen 6 1 (integer) 0 redis 127.0.0.1:6379> bitop and res mon feb wen (integer) 12500001 |
如上例,优点为:
1、节约空间, 1亿人每天的登陆情况,用1亿bit,约1200WByte,约10M 的字符就能表示;
2、计算方便。
拓展:
上一个案例,因为要计算连续登陆,所以用每年做key 而不是uid
|
|
redis()->setBit($uid, $offset, true); // 取出 $resp = redis()->get($uid); $str = unpack("C*", $resp); foreach ($str as $v) { // 二进制串 取出010101这种结果 <a href="//这里要使用0补齐8位$res%20.=%20str_pad(decbin($v),%208,%20'0',%20STR_PAD_LEFT);%20//转成0/1字符串%201代表当天签到过">$res .= str_pad(decbin($v), 8, '0', STR_PAD_LEFT);</a> } |
用UID做key,方便展示每个用户登陆信息。
决定使用哪种方式前,了解位计算函数。
一、基本用法
任何一门程序语言都离不开位运算这个功能,redis虽然不是一门编程语言,但也是一个和编程密切关联的工具。因此位运算自然也是redis中不可或缺的功能。
redis中位运算相关的方法:
- GETBIT key offset:获取第offset位的bit,不存的的比特位返回0。
- SETBIT key offset value:给第offset位设置成value。
- BITCOUNT key [start] [end]:计算key中1的个数。
- BITOP operation destkey key [key]:执行位操作,位操作包含与(AND)、或(OR)、异或(XOR)以及 非(NOT)。
- BITPOS key value [start] [end]:查询key中第一次出现value的位置,start和end表示字符的开始和结束位置。
二、使用示例
以字符串maqian为示例,对应的ASCII码和二进制位如下所示:

对redis而言,它不会和我们一样给每个字符一一对应到二进制数据,对计算机而言,它能看到的就是图中第三行的数据。
2.1 获取比特
给name设置值为maqian,获取前8个字节分别对应m的比特位:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
127.0.0.1:6379> set name maqian
OK
127.0.0.1:6379> getbit name 0
(integer) 0
127.0.0.1:6379> getbit name 1
(integer) 1
127.0.0.1:6379> getbit name 2
(integer) 1
127.0.0.1:6379> getbit name 3
(integer) 0
127.0.0.1:6379> getbit name 4
(integer) 1
127.0.0.1:6379> getbit name 5
(integer) 1
127.0.0.1:6379> getbit name 6
(integer) 0
127.0.0.1:6379> getbit name 7
(integer) 1
|
2.2 计算1的个数
maqian 字符串中1的个数为5+3+4+4+3+5=24个,可以使用redis获取这个值:
|
|
127.0.0.1:6379> bitcount name
(integer) 24
127.0.0.1:6379> bitcount name 0 1 # 只计算前面两个字符ma中1的个数:5 + 3 = 8
(integer) 8
|
2.3 修改比特位
把第m 的第六位变成1,即相当于把m的ascii码+2,此时字符表示的是o :
|
|
127.0.0.1:6379> setbit name 6 1
(integer) 0
127.0.0.1:6379> get name
"oaqian"
|
2.4 逻辑操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
127.0.0.1:6379> set a a
OK
127.0.0.1:6379> set b b
OK
127.0.0.1:6379> bitop and dst a b # 与
(integer) 1
127.0.0.1:6379> get dst
"`"
127.0.0.1:6379> bitop or dst a b # 或
(integer) 1
127.0.0.1:6379> get dst
"c"
127.0.0.1:6379> bitop xor dst a b # 异或
(integer) 1
127.0.0.1:6379> get dst
"x03"
|
想要准确长久记住这些逻辑运算,有一个对应法则口诀:
与:and -> 有0出0,全1出1 例如:1 ,1–>1 1 ,0–>0 0 ,1–>0 0 ,0–>0
或:or -> 有1出1,全0出0 例如:1 ,1–>1 1 ,0–>1 0 ,1–>1 0 ,0–>0
非:not ->有1出0,有0出1 例如:1 -->0 0–>1
与非:nand ->先按与的操作,然后结果取反 例如:1 ,1–>0 1 ,0–>1 0 ,1–>1 0 ,0–>1
或非:nor ->先按或的操作,然后结果取反 例如:1 ,1–>0 1 ,0–>0 0 ,1–>0 0 ,0–>1
异或:xor ->相异为1,相同为0 例如:1 ,1-->0 1 ,0-->1 0 ,1-->1 0 ,0-->0 同或:xnor ->相同为1,相异为0 例如:1 ,1-->1 1 ,0-->0 0 ,1-->0 0 ,0-->1
2.5 查询第一个1的位置
|
|
127.0.0.1:6379> set name maqian
OK
127.0.0.1:6379> bitpos name 1 # 查询整个字符串第一个1的位置
(integer) 1
127.0.0.1:6379> bitpos name 1 2 3 # 查询第三个字符和第四个字符qi中第一次出现1的位置
(integer) 17
|
来源:
https://www.zhihu.com/question/27672245/answer/123641959
https://www.dyxmq.cn/databases/redis/redis-bit-operator.html
「三年博客,如果觉得我的文章对您有用,请帮助本站成长」
共有 0 - redis中setbit(位操作)的实际应用